/*
 * Copyright [2022] [https://www.xiaonuo.vip]
 *
 * Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Snowy源码头部的版权声明。
 * 3.本项目代码可免费商业使用，商业使用请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://www.xiaonuo.vip
 * 5.不可二次分发开源参与同类竞品，如有想法可联系团队xiaonuobase@qq.com商议合作。
 * 6.若您的项目无法满足以上几点，需要更多功能代码，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */
package vip.xiaonuo.biz.modular.cabinetpermission.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
import vip.xiaonuo.auth.core.util.StpClientUtil;
import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
import vip.xiaonuo.biz.core.enums.BizDeviceTypeEnum;
import vip.xiaonuo.biz.modular.cabinetpermission.service.BizCabinetPermissionUtil;
import vip.xiaonuo.biz.modular.certification.CertificationService;
import vip.xiaonuo.biz.modular.certification.result.CertificationVO;
import vip.xiaonuo.biz.modular.user.entity.BizUser;
import vip.xiaonuo.biz.modular.user.mapper.BizUserMapper;
import vip.xiaonuo.common.enums.BizPermissionEnum;
import vip.xiaonuo.biz.modular.cabinetdoor.entity.BizCabinetDoor;
import vip.xiaonuo.biz.modular.cabinetdoor.service.BizCabinetDoorService;
import vip.xiaonuo.biz.modular.cabinetgroup.entity.BizCabinetGroup;
import vip.xiaonuo.biz.modular.cabinetgroup.service.BizCabinetGroupService;
import vip.xiaonuo.biz.modular.cabinetinfo.entity.BizCabinetInfo;
import vip.xiaonuo.biz.modular.cabinetinfo.service.BizCabinetInfoService;
import vip.xiaonuo.biz.modular.cabinetpermission.entity.BizCabinetPermission;
import vip.xiaonuo.biz.modular.cabinetpermission.enums.BizCabinetPermissionEnum;
import vip.xiaonuo.biz.modular.cabinetpermission.mapper.BizCabinetPermissionMapper;
import vip.xiaonuo.biz.modular.cabinetpermission.param.*;
import vip.xiaonuo.biz.modular.cabinetpermission.service.BizCabinetPermissionService;
import vip.xiaonuo.biz.modular.cabinetpermission.vo.BizCabinetDoorPermissionResp;
import vip.xiaonuo.biz.modular.cabinetpermission.vo.BizCabinetPermissionResp;
import vip.xiaonuo.biz.modular.cabinetpermission.vo.ClientAdminCabinetPermission;
import vip.xiaonuo.biz.modular.cabinetpermission.vo.ClientUserCabinetPermission;
import vip.xiaonuo.biz.modular.cabinetpermissionlog.entity.BizCabinetPermissionLog;
import vip.xiaonuo.biz.modular.cabinetpermissionlog.mapper.BizCabinetPermissionLogMapper;
import vip.xiaonuo.common.exception.CommonException;

import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 柜门权限Service接口实现类
 *
 * @author kongxm
 * @date 2022/11/20 17:04
 **/
@Service
public class BizCabinetPermissionServiceImpl extends ServiceImpl<BizCabinetPermissionMapper, BizCabinetPermission> implements BizCabinetPermissionService {

    @Autowired
    private BizCabinetPermissionLogMapper bizCabinetPermissionLogMapper;
    @Autowired
    private BizCabinetGroupService bizCabinetGroupService;
    @Autowired
    private BizCabinetInfoService bizCabinetInfoService;
    @Autowired
    private BizCabinetDoorService bizCabinetDoorService;
    @Autowired
    private BizUserMapper bizUserMapper;
    @Autowired
    private CertificationService certificationService;
    @Autowired
    private BizCabinetPermissionUtil bizCabinetPermissionUtil;


    @Override
    public Page<BizCabinetPermissionResp> page(BizCabinetPermissionPageParam param) {
        if (!StpUtil.hasRoleOr("superAdmin", "sysAdmin")) {
            SaBaseLoginUser saBaseLoginUser = StpLoginUserUtil.getLoginUser();
            param.setUserId(saBaseLoginUser.getId());
        }
        Page<BizCabinetPermissionResp> page = getBaseMapper().page(Page.of(param.getCurrent(), param.getSize()), param);
        page.getRecords().forEach(item -> {
            if (item.getPermEndTime() == null) return;
            // 计算授权天数
            if (item.getPermEndTime() != null && item.getPermStartTime() != null) {
                long permDays = DateUtil.betweenDay(item.getPermEndTime(), item.getPermStartTime(), false);
                item.setPermDays(permDays);
            }
            // 计算剩余天数
            if (item.getPermEndTime() != null) {
                long remainDays = DateUtil.betweenDay(new Date(), item.getPermEndTime(), false);
                remainDays = remainDays < 0 ? 0 : remainDays;
                item.setRemainDays(remainDays);
            }
            // 计算冻结天数
            if (item.getFrozenEndTime() != null && item.getFrozenStartTime() != null) {
                long frozenDays = DateUtil.betweenDay(item.getFrozenEndTime(), item.getFrozenStartTime(), false);
                item.setFrozenDays(frozenDays);
            }
        });
        return page;
    }

    @Override
    public void add(BizCabinetPermissionAddParam bizCabinetPermissionAddParam) {
        bizCabinetPermissionUtil.hasPermission(
                bizCabinetPermissionAddParam.getCabinetGroupId(),
                bizCabinetPermissionAddParam.getCabinetInfoId(),
                bizCabinetPermissionAddParam.getCabinetDoorId()
        );
        BizCabinetPermission bizCabinetPermission = BeanUtil.toBean(bizCabinetPermissionAddParam, BizCabinetPermission.class);
        this.save(bizCabinetPermission);
    }

    @Override
    public void edit(BizCabinetPermissionEditParam bizCabinetPermissionEditParam) {
        bizCabinetPermissionUtil.hasPermission(
                bizCabinetPermissionEditParam.getCabinetGroupId(),
                bizCabinetPermissionEditParam.getCabinetInfoId(),
                bizCabinetPermissionEditParam.getCabinetDoorId()
        );
        BizCabinetPermission bizCabinetPermission = this.queryEntity(bizCabinetPermissionEditParam.getId());
        BeanUtil.copyProperties(bizCabinetPermissionEditParam, bizCabinetPermission);
        this.updateById(bizCabinetPermission);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(List<BizCabinetPermissionIdParam> bizCabinetPermissionIdParamList) {
        bizCabinetPermissionIdParamList.forEach(item -> {
            BizCabinetPermission bizCabinetPermission = getById(item.getId());
            bizCabinetPermissionUtil.hasPermission(
                    bizCabinetPermission.getCabinetGroupId(),
                    bizCabinetPermission.getCabinetInfoId(),
                    bizCabinetPermission.getCabinetDoorId()
            );
        });
        // 执行删除
        this.removeBatchByIds(CollStreamUtil.toList(bizCabinetPermissionIdParamList, BizCabinetPermissionIdParam::getId));
    }

    @Override
    public BizCabinetPermission detail(BizCabinetPermissionIdParam bizCabinetPermissionIdParam) {
        return this.queryEntity(bizCabinetPermissionIdParam.getId());
    }

    @Override
    public BizCabinetPermission queryEntity(String id) {
        BizCabinetPermission bizCabinetPermission = this.getById(id);
        if (ObjectUtil.isEmpty(bizCabinetPermission)) {
            throw new CommonException("柜门权限不存在，id值为：{}", id);
        }
        return bizCabinetPermission;
    }

    /**
     * 待授权	UNAUTHORIZED
     * 已拒绝	REFUSED
     * 已授权	AUTHORIZED
     * 已冻结	FROZEN
     * 已过期	EXPIRED
     *
     * @param permissionId
     * @return
     */
    private void insertPermissionLog(String permissionId, String operation) {
        BizCabinetPermission perm = getById(permissionId);
        BizCabinetPermissionLog permLog = new BizCabinetPermissionLog();
        BeanUtil.copyProperties(perm, permLog, "id");
        permLog.setPermissionId(perm.getId());
        // There must insert the update user because it won't have a update user when user apply a new permission authorization.
        permLog.setUpdateTime(new Date());
        permLog.setUpdateUser(perm.getUpdateUser() == null ? perm.getCreateUser() : perm.getUpdateUser());
        permLog.setOperation(operation);
        bizCabinetPermissionLogMapper.insert(permLog);
    }

    /**
     * @param param
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void authorize(BizCabinetPermissionAuthorizeParam param) {
        Assert.isTrue(DateUtil.compare(param.getPermEndTime(), param.getPermStartTime()) >= 0, "开始时间不能小于结束时间");
        Assert.isTrue(DateUtil.compare(new Date(), param.getPermEndTime()) <= 0, "授权结束时间不能小于当前时间");
        BizCabinetPermission permission = getById(param.getId());
        Assert.notNull(permission, "记录不存在");
        bizCabinetPermissionUtil.hasPermission(
                permission.getCabinetGroupId(),
                permission.getCabinetInfoId(),
                permission.getCabinetDoorId()
        );
        BizUser bizUser = bizUserMapper.selectById(permission.getUserId());
        Assert.notNull(bizUser, "用户不存在");
        Assert.notEmpty(bizUser.getUserNo(), "授权失败，用户{}卡号为空", bizUser.getName());
        Assert.isTrue(BizPermissionEnum.UNAUTHORIZED.name().equalsIgnoreCase(permission.getPermState()), "非法请求");
        BizCabinetPermission permissionForUpdating = new BizCabinetPermission();
        permissionForUpdating.setId(param.getId());
        permissionForUpdating.setPermStartTime(param.getPermStartTime());
        permissionForUpdating.setPermEndTime(param.getPermEndTime());
        //将冻结日期结束时间改为当前日期
        if (permission.getFrozenEndTime() != null) permissionForUpdating.setFrozenEndTime(new Date());
        permissionForUpdating.setPermState(BizPermissionEnum.AUTHORIZED.name());
        updateById(permissionForUpdating);
        // add operation log
        insertPermissionLog(param.getId(), "授权");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchAuthorize(BizCabinetPermissionAuthorizeBatchParam param) {
        Assert.isTrue(DateUtil.compare(param.getPermEndTime(), param.getPermStartTime()) >= 0, "开始时间不能小于结束时间");
        Assert.isTrue(DateUtil.compare(new Date(), param.getPermEndTime()) <= 0, "授权结束时间不能小于当前时间");
        param.getIds().forEach(id -> {
            BizCabinetPermission permission = getById(id);
            Assert.notNull(permission, "记录不存在");

            bizCabinetPermissionUtil.hasPermission(
                    permission.getCabinetGroupId(),
                    permission.getCabinetInfoId(),
                    permission.getCabinetDoorId()
            );

            BizUser bizUser = bizUserMapper.selectById(permission.getUserId());
            Assert.notNull(bizUser, "用户不存在");
            Assert.notEmpty(bizUser.getUserNo(), "批量授权失败，用户{}卡号为空", bizUser.getName());

            if (!BizPermissionEnum.UNAUTHORIZED.name().equalsIgnoreCase(permission.getPermState())) return;//如果未授权，方可授权
            BizCabinetPermission permissionForUpdating = new BizCabinetPermission();
            permissionForUpdating.setId(id);
            permissionForUpdating.setPermStartTime(param.getPermStartTime());
            permissionForUpdating.setPermEndTime(param.getPermEndTime());
            //将冻结日期结束时间改为当前日期
            if (permission.getFrozenEndTime() != null) permissionForUpdating.setFrozenEndTime(new Date());
            permissionForUpdating.setPermState(BizPermissionEnum.AUTHORIZED.name());
            updateById(permissionForUpdating);
            // add operation log
            insertPermissionLog(id, "批量授权");
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchAuthorizeUsers(BizCabinetPermissionAuthorizeBatchUsersParam param) {
        bizCabinetPermissionUtil.hasPermission(
                param.getCabinetGroupId(),
                param.getCabinetInfoId(),
                param.getCabinetDoorId()
        );
        Assert.isTrue(DateUtil.compare(param.getPermEndTime(), param.getPermStartTime()) >= 0, "开始时间不能小于结束时间");
        Assert.isTrue(DateUtil.compare(new Date(), param.getPermEndTime()) <= 0, "授权结束时间不能小于当前时间");
        param.getUserIds().forEach(userId -> {
            BizUser bizUser = bizUserMapper.selectById(userId);
            Assert.notNull(bizUser, "用户不存在");
            Assert.notEmpty(bizUser.getUserNo(), "批量授权失败，用户{}卡号为空", bizUser.getName());
            BizCabinetPermission permission = getBizCabinetPermission(userId, param.getDeviceType(), param.getCabinetGroupId(), param.getCabinetInfoId(), param.getCabinetDoorId());
            permission.setPermStartTime(param.getPermStartTime());
            permission.setPermEndTime(param.getPermEndTime());
            permission.setUserApplyDays(DateUtil.betweenDay(param.getPermEndTime(), param.getPermStartTime(), false));
            permission.setUserApplyTime(new Date());
            permission.setDeviceType(param.getDeviceType());
            permission.setCabinetGroupId(param.getCabinetGroupId());
            permission.setCabinetInfoId(param.getCabinetInfoId());
            permission.setCabinetDoorId(param.getCabinetDoorId());
            if (BizPermissionEnum.FROZEN.name().equals(permission.getPermState())) { // 如果老的状态是冻结，这里要设置解冻时间为当前时间
                permission.setFrozenEndTime(new Date());
            }
            permission.setPermState(BizPermissionEnum.AUTHORIZED.name());
            permission.setCabinetPermissionType(param.getCabinetPermissionType());
            saveOrUpdate(permission);
            insertPermissionLog(permission.getId(), "选择用户授权");
        });
    }

    private BizCabinetPermission getExistOneByCondition(String userId, String deviceType, String cabinetGroupId, String cabinetInfoId, String cabinetDoorId) {
        LambdaQueryWrapper<BizCabinetPermission> condition = new LambdaQueryWrapper<BizCabinetPermission>()
                .eq(BizCabinetPermission::getUserId, userId)
                .eq(BizCabinetPermission::getDeviceType, deviceType)
                .last("limit 1");

        if (BizDeviceTypeEnum.CABINET_GROUP.name().equalsIgnoreCase(deviceType)) {
            condition.eq(BizCabinetPermission::getCabinetGroupId, cabinetGroupId);
        } else if (BizDeviceTypeEnum.CABINET_INFO.name().equalsIgnoreCase(deviceType)) {
            condition.eq(BizCabinetPermission::getCabinetInfoId, cabinetInfoId);
        } else if (BizDeviceTypeEnum.CABINET_DOOR.name().equalsIgnoreCase(deviceType)) {
            condition.eq(BizCabinetPermission::getCabinetDoorId, cabinetDoorId);
        }
        return getOne(condition);
    }

    private BizCabinetPermission getBizCabinetPermission(String userId, String deviceType, String cabinetGroupId, String cabinetInfoId, String cabinetDoorId) {
        BizCabinetPermission existPerm = getExistOneByCondition(userId,
                deviceType,
                cabinetGroupId,
                cabinetInfoId,
                cabinetDoorId);

        BizCabinetPermission permission = new BizCabinetPermission();
        if (existPerm != null) {
            permission.setId(existPerm.getId());
            permission.setPermState(existPerm.getPermState());
        }
        permission.setUserId(userId);
        permission.setDeviceType(deviceType);

        if (BizDeviceTypeEnum.CABINET_GROUP.name().equalsIgnoreCase(deviceType)) {
            permission.setCabinetGroupId(cabinetGroupId);
        } else if (BizDeviceTypeEnum.CABINET_INFO.name().equalsIgnoreCase(deviceType)) {
            permission.setCabinetInfoId(cabinetInfoId);
        } else if (BizDeviceTypeEnum.CABINET_DOOR.name().equalsIgnoreCase(deviceType)) {
            permission.setCabinetDoorId(cabinetDoorId);
        }
        return permission;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refuse(BizCabinetPermissionRefuseParam param) {
        BizCabinetPermission permission = getById(param.getId());
        Assert.notNull(permission, "记录不存在");
        bizCabinetPermissionUtil.hasPermission(
                permission.getCabinetGroupId(),
                permission.getCabinetInfoId(),
                permission.getCabinetDoorId()
        );
        Assert.isTrue(BizPermissionEnum.UNAUTHORIZED.name().equalsIgnoreCase(permission.getPermState()), "非法请求");
        BizCabinetPermission permissionForUpdating = new BizCabinetPermission();
        permissionForUpdating.setId(param.getId());
        permissionForUpdating.setRefuseReason(param.getRefuseReason());
        permissionForUpdating.setPermState(BizPermissionEnum.REFUSED.name());
        updateById(permissionForUpdating);
        // add operation log
        insertPermissionLog(param.getId(), "拒绝");
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchRefuse(BizCabinetPermissionRefuseBatchParam param) {
        param.getIds().forEach(id -> {
            BizCabinetPermission permission = getById(id);
            Assert.notNull(permission, "记录不存在");
            bizCabinetPermissionUtil.hasPermission(
                    permission.getCabinetGroupId(),
                    permission.getCabinetInfoId(),
                    permission.getCabinetDoorId()
            );
            if (!BizPermissionEnum.UNAUTHORIZED.name().equalsIgnoreCase(permission.getPermState())) return;//如果不是未授权，跳过。
            BizCabinetPermission permissionForUpdating = new BizCabinetPermission();
            permissionForUpdating.setId(id);
            permissionForUpdating.setRefuseReason(param.getRefuseReason());
            permissionForUpdating.setPermState(BizPermissionEnum.REFUSED.name());
            updateById(permissionForUpdating);
            // add operation log
            insertPermissionLog(id, "批量拒绝");
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void freeze(BizCabinetPermissionFreezeParam param) {
        BizCabinetPermission permission = getById(param.getId());
        Assert.notNull(permission, "记录不存在");
        bizCabinetPermissionUtil.hasPermission(
                permission.getCabinetGroupId(),
                permission.getCabinetInfoId(),
                permission.getCabinetDoorId()
        );
        BizUser bizUser = bizUserMapper.selectById(permission.getUserId());
        Assert.notNull(bizUser, "用户不存在");
        Assert.notEmpty(bizUser.getUserNo(), "冻结失败，用户{}卡号为空", bizUser.getName());
        Assert.isFalse(BizPermissionEnum.FROZEN.name().equalsIgnoreCase(permission.getPermState()), "非法请求");
        BizCabinetPermission permissionForUpdating = new BizCabinetPermission();
        permissionForUpdating.setId(param.getId());
        permissionForUpdating.setPermState(BizPermissionEnum.FROZEN.name());
        permissionForUpdating.setFrozenStartTime(new Date());
        permissionForUpdating.setFrozenEndTime(DateUtil.offsetDay(permissionForUpdating.getFrozenStartTime(), param.getFrozenDays()));
        // 如果这里不设置，那无法设置为空的拒绝原因 for fixing bug.
        permissionForUpdating.setFrozenReason(StringUtils.isEmpty(param.getFrozenReason()) ? "" : param.getFrozenReason());
        updateById(permissionForUpdating);
        // add operation log
        insertPermissionLog(param.getId(), "冻结");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchFreezeUsers(BizCabinetPermissionFreezeBatchUsersParam param) {
        bizCabinetPermissionUtil.hasPermission(
                param.getCabinetGroupId(),
                param.getCabinetInfoId(),
                param.getCabinetDoorId()
        );
        param.getUserIds().forEach(userId -> {
            BizUser bizUser = bizUserMapper.selectById(userId);
            Assert.notNull(bizUser, "用户不存在");
            Assert.notEmpty(bizUser.getUserNo(), "冻结失败，用户{}卡号为空", bizUser.getName());
            BizCabinetPermission permission = getBizCabinetPermission(userId,
                    param.getDeviceType(), param.getCabinetGroupId(), param.getCabinetInfoId(), param.getCabinetDoorId());
            permission.setFrozenStartTime(new Date());
            permission.setFrozenEndTime(DateUtil.offsetDay(permission.getFrozenStartTime(), param.getFrozenDays()));
            permission.setDeviceType(param.getDeviceType());
            permission.setCabinetGroupId(param.getCabinetGroupId());
            permission.setCabinetInfoId(param.getCabinetInfoId());
            permission.setCabinetDoorId(param.getCabinetDoorId());
            permission.setFrozenReason(param.getFrozenReason());
            permission.setPermState(BizPermissionEnum.FROZEN.name());
            permission.setCabinetPermissionType(param.getCabinetPermissionType());
            saveOrUpdate(permission);
            insertPermissionLog(permission.getId(), "选择用户冻结");
        });
    }

    /**
     * 管理员也可以手动给用户的授权进行解冻，
     * 解冻后，将冻结结束日期修改为当天，
     * 如果此时的授权还未过期，这状态调整为“已授权”，
     * 如果授权已过期，则状态调整为“已过期”，
     * 如果还未授权，则状态为“待审核”。
     *
     * @param param
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unfreeze(BizCabinetPermissionUnfreezeParam param) {
        BizCabinetPermission permission = getById(param.getId());
        bizCabinetPermissionUtil.hasPermission(
                permission.getCabinetGroupId(),
                permission.getCabinetInfoId(),
                permission.getCabinetDoorId()
        );
        Assert.notNull(permission, "记录不存在");
        Assert.isTrue(BizPermissionEnum.FROZEN.name().equalsIgnoreCase(permission.getPermState()), "非法请求");
        // justice the state after unfreezing
        String permState = "";
        if (permission.getPermStartTime() == null || permission.getPermEndTime() == null) { //如果还未授权，则状态为“待审核”(待授权)。
            permState = BizPermissionEnum.UNAUTHORIZED.name();
        } else if (DateUtil.compare(permission.getPermEndTime(), new Date()) > 0) { //如果此时的授权还未过期，这状态调整为“已授权”，
            permState = BizPermissionEnum.AUTHORIZED.name();
        } else if (DateUtil.compare(new Date(), permission.getPermEndTime()) > 0) { //如果授权已过期，则状态调整为“已过期”，
            permState = BizPermissionEnum.EXPIRED.name();
        }
        BizCabinetPermission permissionForUpdating = new BizCabinetPermission();
        permissionForUpdating.setId(param.getId());
        permissionForUpdating.setFrozenEndTime(new Date()); //解冻后，将冻结结束日期修改为当天，
        permissionForUpdating.setPermState(permState);
        updateById(permissionForUpdating);
        // add operation log
        insertPermissionLog(param.getId(), "解冻");
    }

    /**
     * example one
     * 姓名     组柜权限     柜子权限      柜门权限     柜门权限是否独立设置
     * 张三                               Y
     * <p>
     * example two
     * 张三                  Y                           N
     * <p>
     * example three
     * 张三                  Y                           Y
     *
     * @param param
     * @return
     */
    @Override
    public BizCabinetDoorPermissionResp listPermission(BizCabinetPermissionListParam param) {
        BizCabinetDoor bizCabinetDoor = bizCabinetDoorService.getById(param.getCabinetDoorId());
        Assert.notNull(bizCabinetDoor, "柜门id={},不存在", param.getCabinetDoorId());

        BizCabinetDoorPermissionResp resp = new BizCabinetDoorPermissionResp();
        param.setCabinetInfoId(bizCabinetDoor.getCabinetInfoId());
        param.setCabinetGroupId(bizCabinetDoor.getCabinetGroupId());
        List<BizCabinetPermissionResp> dataList = getBaseMapper().listPermission(param);
        List<String> userIds = dataList.stream().map(BizCabinetPermission::getUserId).distinct().collect(Collectors.toList());

        userIds.forEach(userId -> {
            // 1、set permission for cabinet door.
            if (bizCabinetDoor.getSeparatedPermission()) {
                // 1.1 The cabinet door has the separated permission. get permission from itself.
                addPermissionFromCabinetDoor(resp, dataList, userId, param.getCabinetDoorId());
                return;
            }

            // 1.2、otherwise, get the permission from its parents.
            boolean findPermissionFromCabinetDoor = addPermissionFromCabinetDoor(resp, dataList, userId, param.getCabinetDoorId());
            if (!findPermissionFromCabinetDoor) {
                boolean findPermissionFromCabinetInfo = addPermissionFromCabinetInfo(resp, dataList, userId);
                if (!findPermissionFromCabinetInfo) addPermissionFromCabinetGroup(resp, dataList, userId);
            }

            // 2、set permission for cabinet info.
            BizCabinetDoorPermissionResp.Item permissionOnCabinetInfo = getPermissionFromCabinetInfo(dataList, userId);
            resp.addCabinetInfoPermission(permissionOnCabinetInfo);

            // 3、set permission for cabinet group.
            BizCabinetDoorPermissionResp.Item permissionOnCabinetGroup = getPermissionFromCabinetGroup(dataList, userId);
            resp.addCabinetGroupPermission(permissionOnCabinetGroup);
        });
        return resp;
    }

    /**
     * if the permission of the cabinet door is configured as a separated permission,
     * the permission of the cabinet door must be from itself, it can't extend from the cabinet info or the cabinet group.
     *
     * @param resp
     * @param dataList
     * @param userId
     * @param cabinetDoorId
     */
    private boolean addPermissionFromCabinetDoor(BizCabinetDoorPermissionResp resp, List<BizCabinetPermissionResp> dataList, String userId, String cabinetDoorId) {
        Optional<BizCabinetPermissionResp> permissionOnCabinetDoor = dataList.stream().
                filter(data -> StringUtils.isNotEmpty(data.getCabinetDoorId())
                        && cabinetDoorId.equals(data.getCabinetDoorId())
                        && data.getDeviceType().equals(BizDeviceTypeEnum.CABINET_DOOR.name())
                        && data.getUserId().equals(userId))
                .findAny();
        if (permissionOnCabinetDoor.isPresent()) {
            BizCabinetDoorPermissionResp.Item item = BizCabinetDoorPermissionResp.Item.builder().build();
            BeanUtil.copyProperties(permissionOnCabinetDoor.get(), item);
            resp.addCabinetDoorPermission(item);
            return true;
        }
        return false;
    }

    /**
     * get permission of the cabinet door from its parent(cabinet info)
     *
     * @param resp
     * @param dataList
     * @param userId
     * @return
     */
    private boolean addPermissionFromCabinetInfo(BizCabinetDoorPermissionResp resp, List<BizCabinetPermissionResp> dataList, String userId) {
        BizCabinetDoorPermissionResp.Item permCabinetInfo = getPermissionFromCabinetInfo(dataList, userId);
        if (permCabinetInfo == null) return false;
        permCabinetInfo.setExtInfo("权限继承自柜子");
        resp.addCabinetDoorPermission(permCabinetInfo);
        return true;
    }

    private BizCabinetDoorPermissionResp.Item getPermissionFromCabinetInfo(List<BizCabinetPermissionResp> dataList, String userId) {
        Optional<BizCabinetPermissionResp> permissionOnCabinetInfo = dataList.stream().
                filter(data ->
                        StringUtils.isNotEmpty(data.getCabinetInfoId())
                                && data.getDeviceType().equals(BizDeviceTypeEnum.CABINET_INFO.name())
                                && data.getUserId().equals(userId))
                .findAny();
        if (permissionOnCabinetInfo.isPresent()) {
            BizCabinetDoorPermissionResp.Item permCabinetDoor = BizCabinetDoorPermissionResp.Item.builder().build();
            BeanUtil.copyProperties(permissionOnCabinetInfo.get(), permCabinetDoor);
            return permCabinetDoor;
        }
        return null;
    }

    /**
     * get permission of the cabinet door from its parent(cabinet group)
     *
     * @param resp
     * @param dataList
     * @param userId
     */
    private void addPermissionFromCabinetGroup(BizCabinetDoorPermissionResp resp, List<BizCabinetPermissionResp> dataList, String userId) {
        BizCabinetDoorPermissionResp.Item permCabinetGroup = getPermissionFromCabinetGroup(dataList, userId);
        if (permCabinetGroup == null) return;
        permCabinetGroup.setExtInfo("权限继承自组柜");
        resp.addCabinetDoorPermission(permCabinetGroup);
    }

    private BizCabinetDoorPermissionResp.Item getPermissionFromCabinetGroup(List<BizCabinetPermissionResp> dataList, String userId) {
        Optional<BizCabinetPermissionResp> permissionOnCabinetGroup = dataList.stream().
                filter(data -> StringUtils.isNotEmpty(data.getCabinetGroupId())
                        && data.getDeviceType().equals(BizDeviceTypeEnum.CABINET_GROUP.name())
                        && data.getUserId().equals(userId))
                .findAny();
        if (permissionOnCabinetGroup.isPresent()) {
            BizCabinetDoorPermissionResp.Item permCabinetDoor = BizCabinetDoorPermissionResp.Item.builder().build();
            BeanUtil.copyProperties(permissionOnCabinetGroup.get(), permCabinetDoor);
            return permCabinetDoor;
        }
        return null;
    }

    public boolean hasDoorPermWithCurrentUser(boolean isSeparatedPermission, String cabinetDoorId,
                                              String cabinetId, String cabinetGroupId, String userId) {
        // 如果柜门是独立权限，查询柜门权限
        if (isSeparatedPermission) return hasPerm(cabinetDoorId, BizDeviceTypeEnum.CABINET_DOOR.name(), userId);
        // 非独立权限依次从下往上查询
        if (hasPerm(cabinetDoorId, BizDeviceTypeEnum.CABINET_DOOR.name(), userId)) return true;
        // 查询柜子权限
        if (hasPerm(cabinetId, BizDeviceTypeEnum.CABINET_INFO.name(), userId)) return true;
        //查询组柜权限
        return hasPerm(cabinetGroupId, BizDeviceTypeEnum.CABINET_GROUP.name(), userId);
    }

    private boolean hasPerm(String id, String deviceType, String userId) {
        if (StringUtils.isEmpty(id)) return false;
        LambdaQueryWrapper<BizCabinetPermission> lambdaQueryWrapper =
                new LambdaQueryWrapper<BizCabinetPermission>()
                        .eq(BizCabinetPermission::getDeviceType, deviceType)
                        .eq(BizCabinetPermission::getPermState, BizPermissionEnum.AUTHORIZED.name())
                        .eq(BizCabinetPermission::getUserId, userId);
        if (BizDeviceTypeEnum.CABINET_GROUP.name().equals(deviceType)) {
            lambdaQueryWrapper.eq(BizCabinetPermission::getCabinetGroupId, id);
        } else if (BizDeviceTypeEnum.CABINET_INFO.name().equals(deviceType)) {
            lambdaQueryWrapper.eq(BizCabinetPermission::getCabinetInfoId, id);
        } else if (BizDeviceTypeEnum.CABINET_DOOR.name().equals(deviceType)) {
            lambdaQueryWrapper.eq(BizCabinetPermission::getCabinetDoorId, id);
        }
        long permCount = count(lambdaQueryWrapper);
        return permCount > 0;
    }

    @Override
    public Page<ClientUserCabinetPermission> listForClientUser(ClientCabinetPermissionPageParam param) {
        String userId = StpClientUtil.getLoginIdAsString();
        param.setUserId(userId);
        Page<ClientUserCabinetPermission> permList = getBaseMapper()
                .pageForClientUser(Page.of(param.getCurrent(), param.getSize()), param);
        permList.getRecords().forEach(perm -> {
            if (BizDeviceTypeEnum.CABINET_GROUP.name().equals(perm.getDeviceType())) {
                setCabinetGroupRegion(perm);
            } else if (BizDeviceTypeEnum.CABINET_INFO.name().equals(perm.getDeviceType())) {
                setCabinetInfoRegion(perm); // 如果柜子上没有信息，就组柜的
            } else if (BizDeviceTypeEnum.CABINET_DOOR.name().equals(perm.getDeviceType())) {
                setCabinetDoorRegion(perm);
            }
        });
        return permList;
    }

    private void setCabinetDoorRegion(ClientUserCabinetPermission perm) {
        if (StringUtils.isEmpty(perm.getAreaNameCabinetDoor())
                && StringUtils.isEmpty(perm.getBuildingNameCabinetDoor())
                && StringUtils.isEmpty(perm.getRoomNameCabinetDoor())) {
            setCabinetInfoRegion(perm);
        } else {
            perm.setAreaName(perm.getAreaNameCabinetDoor());
            perm.setBuildingName(perm.getBuildingNameCabinetDoor());
            perm.setRoomName(perm.getRoomNameCabinetDoor());
        }
    }

    private void setCabinetGroupRegion(ClientUserCabinetPermission perm) {
        perm.setAreaName(perm.getAreaNameCabinetGroup());
        perm.setBuildingName(perm.getBuildingNameCabinetGroup());
        perm.setRoomName(perm.getRoomNameCabinetGroup());
    }

    private void setCabinetInfoRegion(ClientUserCabinetPermission perm) {
        if (StringUtils.isEmpty(perm.getAreaNameCabinetInfo())
                && StringUtils.isEmpty(perm.getBuildingNameCabinetInfo())
                && StringUtils.isEmpty(perm.getRoomNameCabinetInfo())) {
            setCabinetGroupRegion(perm);
        } else {
            perm.setAreaName(perm.getAreaNameCabinetInfo());
            perm.setBuildingName(perm.getBuildingNameCabinetInfo());
            perm.setRoomName(perm.getRoomNameCabinetInfo());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean applyPermForClientUser(ClientCabinetPermissionApplyParam param) {
        Assert.notEmpty(param.getDeviceType(), "申请类型不能为空");
        Assert.isTrue(param.getUserApplyDays() != null && param.getUserApplyDays() > 0, "用户申请天数必须大于0");

        String userId = StpClientUtil.getLoginIdAsString();

        if (BizDeviceTypeEnum.CABINET_GROUP.name().equals(param.getDeviceType())) {
            Assert.notEmpty(param.getCabinetGroupId(), "组柜id不能为空");
            BizCabinetGroup bizCabinetGroup = bizCabinetGroupService.getById(param.getCabinetGroupId());
            Assert.isTrue(bizCabinetGroup.getState() == 1, "组柜不可用");
            if (bizCabinetGroup.getMaxPermDays() != null) {
                Assert.isTrue(bizCabinetGroup.getMaxPermDays() != 0, "本组柜权限无法自主申请");
                Assert.isTrue(param.getUserApplyDays() <= bizCabinetGroup.getMaxPermDays(), "用户申请天数不可以大于组柜允许最大天数");
            }
            BizCabinetPermission perm = getOne(new LambdaQueryWrapper<BizCabinetPermission>()
                    .eq(BizCabinetPermission::getUserId, userId)
                    .eq(BizCabinetPermission::getDeleteFlag, "NOT_DELETE")
                    .in(BizCabinetPermission::getPermState, BizPermissionEnum.UNAUTHORIZED.name(), BizPermissionEnum.AUTHORIZED.name(), BizPermissionEnum.FROZEN.name())
                    .eq(BizCabinetPermission::getCabinetGroupId, param.getCabinetGroupId())
                    .eq(BizCabinetPermission::getDeviceType, param.getDeviceType()).last("limit 1"));
            if (perm != null) {
                throw new IllegalArgumentException(String.format("该组柜的许可您已申请，当前状态为%s，不可重复申请！", BizCabinetPermissionEnum.getValue(perm.getPermState())));
            }
        } else if (BizDeviceTypeEnum.CABINET_INFO.name().equals(param.getDeviceType())) {
            Assert.notEmpty(param.getCabinetInfoId(), "柜子id不能为空");
            BizCabinetInfo bizCabinetInfo = bizCabinetInfoService.getById(param.getCabinetInfoId());
            Assert.isTrue(bizCabinetInfo.getState() == 1, "柜子不可用");
            Integer maxPermDays = bizCabinetInfo.getMaxPermDays();
            if (maxPermDays == null) {// 取组柜上的天数
                BizCabinetGroup bizCabinetGroup = bizCabinetGroupService.getOne(new LambdaQueryWrapper<BizCabinetGroup>()
                        .eq(BizCabinetGroup::getState, 1).eq(BizCabinetGroup::getId, bizCabinetInfo.getCabinetGroupId()));
                maxPermDays = bizCabinetGroup == null ? null : bizCabinetGroup.getMaxPermDays();
            }
            if (maxPermDays != null) {
                Assert.isTrue(maxPermDays != 0, "本柜子权限无法自主申请");
                Assert.isTrue(param.getUserApplyDays() <= maxPermDays, "用户申请天数不可以大于柜子允许最大天数");
            }
            BizCabinetPermission perm = getOne(new LambdaQueryWrapper<BizCabinetPermission>()
                    .eq(BizCabinetPermission::getUserId, userId)
                    .eq(BizCabinetPermission::getDeleteFlag, "NOT_DELETE")
                    .in(BizCabinetPermission::getPermState, BizPermissionEnum.UNAUTHORIZED.name(), BizPermissionEnum.AUTHORIZED.name(), BizPermissionEnum.FROZEN.name())
                    .eq(BizCabinetPermission::getCabinetInfoId, param.getCabinetInfoId())
                    .eq(BizCabinetPermission::getDeviceType, param.getDeviceType()).last("limit 1"));
            if (perm != null) {
                throw new IllegalArgumentException(String.format("该柜子的许可您已申请，当前状态为%s，不可重复申请！", BizCabinetPermissionEnum.getValue(perm.getPermState())));
            }
        } else if (BizDeviceTypeEnum.CABINET_DOOR.name().equals(param.getDeviceType())) {
            Assert.notEmpty(param.getCabinetDoorId(), "柜门id不能为空");
            BizCabinetDoor bizCabinetDoor = bizCabinetDoorService.getById(param.getCabinetDoorId());
            Assert.isTrue(bizCabinetDoor.getState() == 1, "柜门不可用");
            Integer maxPermDays = bizCabinetDoor.getMaxPermDays();
            BizCabinetInfo bizCabinetInfo = null;
            if (maxPermDays == null) {// 取柜子上的天数
                bizCabinetInfo = bizCabinetInfoService.getOne(new LambdaQueryWrapper<BizCabinetInfo>()
                        .eq(BizCabinetInfo::getState, 1).eq(BizCabinetInfo::getId, bizCabinetDoor.getCabinetInfoId()));
                maxPermDays = bizCabinetInfo == null ? null : bizCabinetInfo.getMaxPermDays();
            }
            if (bizCabinetInfo != null && maxPermDays == null) {// 取组柜上的天数
                BizCabinetGroup bizCabinetGroup = bizCabinetGroupService.getOne(new LambdaQueryWrapper<BizCabinetGroup>()
                        .eq(BizCabinetGroup::getState, 1).eq(BizCabinetGroup::getId, bizCabinetInfo.getCabinetGroupId()));
                maxPermDays = bizCabinetGroup == null ? null : bizCabinetGroup.getMaxPermDays();
            }
            if (maxPermDays != null) {
                Assert.isTrue(maxPermDays != 0, "本柜门权限无法自主申请");
                Assert.isTrue(param.getUserApplyDays() <= maxPermDays, "用户申请天数不可以大于柜门允许最大天数");
            }
            BizCabinetPermission perm = getOne(new LambdaQueryWrapper<BizCabinetPermission>()
                    .eq(BizCabinetPermission::getUserId, userId)
                    .eq(BizCabinetPermission::getDeleteFlag, "NOT_DELETE")
                    .in(BizCabinetPermission::getPermState, BizPermissionEnum.UNAUTHORIZED.name(), BizPermissionEnum.AUTHORIZED.name(), BizPermissionEnum.FROZEN.name())
                    .eq(BizCabinetPermission::getCabinetDoorId, param.getCabinetDoorId())
                    .eq(BizCabinetPermission::getDeviceType, param.getDeviceType()).last("limit 1"));
            if (perm != null) {
                throw new IllegalArgumentException(String.format("该柜门的许可您已申请，当前状态为%s，不可重复申请！", BizCabinetPermissionEnum.getValue(perm.getPermState())));
            }
        }

        BizCabinetPermission persistPerm = getBizCabinetPermission(userId, param.getDeviceType(),
                param.getCabinetGroupId(), param.getCabinetInfoId(), param.getCabinetDoorId());
        BeanUtil.copyProperties(param, persistPerm);
        persistPerm.setId(null); // 对于权限的申请，必须是新增，而不是更新
        persistPerm.setUserId(userId);
        persistPerm.setUserApplyTime(new Date());
        persistPerm.setUserApplyDays(param.getUserApplyDays());
        persistPerm.setPermState(BizCabinetPermissionEnum.UNAUTHORIZED.name());
        return save(persistPerm);
    }

    @Override
    public Page<ClientAdminCabinetPermission> pagePermissionForAdmin(ClientAdminCabinetPermissionPageParam param) {
        String userId = StpClientUtil.getLoginIdAsString();
        param.setAdminUserId(userId);
        return getBaseMapper().pagePermissionForAdmin(Page.of(param.getCurrent(), param.getSize()), param);
    }

    @Override
    public boolean grantPermForClientAdmin(BizCabinetPermissionAuthorizeParam param) {
        Assert.notEmpty(param.getId(), "id不能为空");
        Assert.notNull(param.getPermStartTime(), "开始时间不能为空");
        Assert.notNull(param.getPermEndTime(), "结束时间不能为空");
        BizCabinetPermission permission = getById(param.getId());
        Assert.notNull(permission, "不存在的记录");
        Assert.isTrue("NOT_DELETE".equals(permission.getDeleteFlag()), "无效的记录");
        Assert.isTrue(DateUtil.compare(param.getPermEndTime(), param.getPermStartTime()) >= 0, "开始时间不能小于结束时间");
        Assert.isTrue(BizCabinetPermissionEnum.UNAUTHORIZED.name().equals(permission.getPermState()), "该申请已被处理");
        BizCabinetPermission perm = new BizCabinetPermission();
        perm.setId(param.getId());
        perm.setPermStartTime(param.getPermStartTime());
        perm.setPermEndTime(param.getPermEndTime());
        perm.setPermState(BizCabinetPermissionEnum.AUTHORIZED.name());
        return updateById(perm);
    }

    @Override
    public List<CertificationVO> listCertification(String userId) {
        BizUser user = bizUserMapper.selectById(userId);
        if (user == null) return ListUtil.empty();
        return certificationService.queryCertificationList(user.getAccount());
    }
}
